﻿using Devil;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace GameToolkit
{
    public interface ImageEffectPassSetup
    {
        void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData);
    }

    /// <summary>
    /// 传输材质属性
    /// </summary>
    public interface ImageEffectPropertyBlock
    {
        void OnSetupPropertyBlock(MaterialPropertyBlock block);
    }

    /// <summary>
    /// image effect 重绘方法
    /// </summary>
    public interface ImageEffectPassBlitter
    {
        void Blit(CommandBuffer cmd, RTHandle source, RTHandle dest, ImageEffectPass.PassData passData);
    }

    [System.Serializable, VolumeComponentMenu("Image Effects/Base Configuration")]
    sealed public class ImageEffectPass : BaseVolumeComponent, ImageEffectPassBlitter
    {
#if UNITY_EDITOR
        static readonly ProfilingSampler PROFILING = new ProfilingSampler("Image Effect Pass");
#endif

        public enum InjectionPoint
        {
            /// <summary>
            /// Inject a full screen pass before transparents are rendered.
            /// </summary>
            BeforeRenderingTransparents = RenderPassEvent.BeforeRenderingTransparents,

            /// <summary>
            /// Inject a full screen pass before post processing is rendered.
            /// </summary>
            BeforeRenderingPostProcessing = RenderPassEvent.BeforeRenderingPostProcessing,

            /// <summary>
            /// Inject a full screen pass after post processing is rendered.
            /// </summary>
            AfterRenderingPostProcessing = RenderPassEvent.AfterRenderingPostProcessing,

            BeforeRenderingDeferredLights = RenderPassEvent.BeforeRenderingDeferredLights,

            AfterRenderingDeferredLights = RenderPassEvent.AfterRenderingDeferredLights,
        }

        [System.Serializable]
        public struct PassData
        {
            public InjectionPoint renderPassEvent;
            public Material material;
            public int passIndex;
            public int sortOrder;
            public ScriptableRenderPassInput requirements;
            public bool overwrite;

            public PassData(InjectionPoint renderEvent, Material material, int pass, bool overwrite, ScriptableRenderPassInput requirements, int sortOrder)
            {
                this.renderPassEvent = renderEvent;
                this.requirements = requirements;
                this.material = material;
                this.passIndex = pass;
                this.sortOrder = sortOrder;
                this.overwrite = overwrite;
            }

            public PassData(InjectionPoint renderEvent, Material material, bool overwrite) : this(renderEvent, material, 0, overwrite, 0, 0)
            {
            }

            public PassData(InjectionPoint renderEvent, Material material, int pass) : this(renderEvent, material, pass, false, 0, 0)
            {
            }

            public PassData(InjectionPoint renderEvent, Material material, ScriptableRenderPassInput requirements) : this(renderEvent, material, 0, false, requirements, 0)
            {
            }

            public PassData(InjectionPoint renderEvent, Material material) : this(renderEvent, material, 0, false, 0, 0)
            {
            }

            public override int GetHashCode()
            {
                var id1 = MathExt.Hash((int)renderPassEvent, material == null ? 0 : material.GetInstanceID());
                var id2 = MathExt.Hash(passIndex, sortOrder);
                var id3 = MathExt.Hash((int)requirements, overwrite ? 1 : 0);
                return MathExt.Hash(id1, id2, id3);
            }

            internal bool IsTheSamePass(PassData other)
            {
                return renderPassEvent == other.renderPassEvent && material == other.material && passIndex == other.passIndex;
            }
        }

        struct PassInstance
        {
            public PassData data;
            public ImageEffectPassSetup setup;
            public ImageEffectPassBlitter bliter;
            public ImageEffectPropertyBlock block;
        }

        public static readonly int sid_BlitTexture = Shader.PropertyToID("_BlitTexture");
        public static readonly int sid_BlitScaleBias = Shader.PropertyToID("_BlitScaleBias");

        [SerializeField]
        BoolParameter m_EnableImageEffectPass = new BoolParameter(false);

        [SerializeField]
        VolumeParameter<EMipmapQuality> m_Resolution = new VolumeParameter<EMipmapQuality> { value = EMipmapQuality.FullRes };

        RTHandle mColor1;
        RTHandle mColor2;
        List<PassInstance> mPasses;
        MaterialPropertyBlock mBlock;
        public MaterialPropertyBlock PropertyBlock => mBlock;

        public void EnqueueBliterPass(PassData pass, ImageEffectPassSetup setup = null)
        {
            EnqueueBliterPass(pass, null, null, setup);
        }

        public void EnqueueBliterPass(PassData pass, ImageEffectPropertyBlock block, ImageEffectPassSetup setup = null)
        {
            EnqueueBliterPass(pass, block, null, null);
        }

        public void EnqueueBliterPass(PassData pass, ImageEffectPassBlitter bliter, ImageEffectPassSetup setup = null)
        {
            EnqueueBliterPass(pass, null, bliter, setup);
        }

        public void EnqueueBliterPass(PassData pass, ImageEffectPropertyBlock block, ImageEffectPassBlitter bliter, ImageEffectPassSetup setup)
        {
            var index = 0;
            PassInstance pinst;
            pinst.data = pass;
            pinst.block = block;
            pinst.bliter = bliter == null ? this : bliter;
            pinst.setup = setup;
            for (int i = mPasses.Count - 1; i >= 0; i--)
            {
                if (mPasses[i].data.renderPassEvent < pass.renderPassEvent || 
                    mPasses[i].data.renderPassEvent == pass.renderPassEvent && mPasses[i].data.sortOrder <= pass.sortOrder)
                {
                    index = i + 1;
                    break;
                }
            }
            if (index < mPasses.Count)
                mPasses.Insert(index, pinst);
            else
                mPasses.Add(pinst);
        }

        public override bool IsActive()
        {
            if (m_EnableImageEffectPass.overrideState)
                return active && m_EnableImageEffectPass.value;
            else
                return active;
        }

        public override void OnActivite(CustomVolumeRenderer volr)
        {
            if (mPasses == null)
                mPasses = new List<PassInstance>();
        }

        public override void OnDeactivite(CustomVolumeRenderer volr)
        {
        }

        public override void OnSetupVolumePasses(CustomVolumeRenderer volr)
        {
            mPasses.Clear();
            var count = volr.ActiveVolumeCount;
            for (int i = 0; i < count; i++)
            {
                var eff = volr.GetActiveVolumeComponent(i) as ImageEffectPassData;
                if (eff != null)
                    eff.OnSetupBlitterPassData(this);
            }
            int start = 0;
            int end = mPasses.Count - 1;
            for (int i = 0; i <= end; i++)
            {
                if(mPasses[start].data.renderPassEvent != mPasses[i].data.renderPassEvent)
                {
                    var pass = volr.Enqueue<ImgFxPass>(this, (RenderPassEvent)mPasses[start].data.renderPassEvent, 0);
                    if (pass != null)
                    {
                        pass.passData = this;
                        pass.passStart = start;
                        pass.passEnd = i - 1;
                        start = i;
                    }
                }
                if(i == end)
                {
                    var pass = volr.Enqueue<ImgFxPass>(this, (RenderPassEvent)mPasses[start].data.renderPassEvent, 0);
                    if (pass != null)
                    {
                        pass.passData = this;
                        pass.passStart = start;
                        pass.passEnd = i;
                    }
                }
            }
        }

        void ImageEffectPassBlitter.Blit(CommandBuffer cmd, RTHandle source, RTHandle dest, PassData passData)
        {
            CoreUtils.SetRenderTarget(cmd, dest);
            if (source != null)
                mBlock.SetTexture(sid_BlitTexture, source);
            cmd.DrawProcedural(Matrix4x4.identity, passData.material, passData.passIndex, MeshTopology.Triangles, 3, 1, mBlock);
        }

        class ImgFxPass : ScriptableRenderPass
        {
            internal ImageEffectPass passData { get; set; }
            internal int passStart { get; set; }
            internal int passEnd { get; set; }

            public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
            {
                var desc = renderingData.cameraData.cameraTargetDescriptor;
                desc.msaaSamples = 1;
                desc.depthBufferBits = (int)DepthBits.None;
                var res = (int)passData.m_Resolution.value;
                desc.width >>= res;
                desc.height >>= res;
                RenderingUtils.ReAllocateIfNeeded(ref passData.mColor1, desc, FilterMode.Bilinear, name: "_color1");
                if (passEnd > passStart)
                    RenderingUtils.ReAllocateIfNeeded(ref passData.mColor2, desc, FilterMode.Bilinear, name: "_color2");
                ScriptableRenderPassInput requirements = 0;
                for (int i = passStart; i <= passEnd; i++)
                {
                    requirements |= passData.mPasses[i].data.requirements;
                    passData.mPasses[i].setup?.OnCameraSetup(cmd, ref renderingData);
                }
                ConfigureInput(requirements);
            }

            public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
            {
                var cmd = CommandBufferPool.Get();

#if UNITY_EDITOR
                using (new ProfilingScope(cmd, PROFILING))
#endif
                {
                    if (passData.mBlock == null)
                    {
                        passData.mBlock = new MaterialPropertyBlock();
                    }
                    else
                    {
                        passData.mBlock.Clear();
                    }
                    ref var cameraData = ref renderingData.cameraData;
                    var scaleBias = new Vector4(1, 1, 0, 0);
                    RTHandle color = cameraData.renderer.cameraColorTargetHandle;
                    var rt = passData.mColor1;
                    passData.mBlock.SetVector(sid_BlitScaleBias, scaleBias);
                    bool biliner = passData.m_Resolution.value > 0;
                    if (biliner)
                    {
                        CoreUtils.SetRenderTarget(cmd, rt);
                        Blitter.BlitTexture(cmd, color, scaleBias, 0.0f, false);
                        color = rt;
                    }
                    for (int i = passStart; i <= passEnd; i++)
                    {
                        var pass = passData.mPasses[i];
                        if (pass.data.overwrite)
                        {
                            rt = color;
                            color = null;
                        }
                        else
                        {
                            rt = color == passData.mColor1 ? passData.mColor2 : passData.mColor1;
                        }
                        if (pass.block != null)
                        {
                            pass.block.OnSetupPropertyBlock(passData.mBlock);
                        }
                        pass.bliter.Blit(cmd, color, rt, pass.data);
                        color = rt;
                    }
                    if (rt != cameraData.renderer.cameraColorTargetHandle)
                    {
                        CoreUtils.SetRenderTarget(cmd, cameraData.renderer.cameraColorTargetHandle);
                        Blitter.BlitTexture(cmd, rt, scaleBias, 0.0f, biliner);
                    }
                }
                context.ExecuteCommandBuffer(cmd);
                CommandBufferPool.Release(cmd);
            }
        }
    }
}
